package com.dd.stack.redis.config.redisConfig.lock;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @Author liuxianmeng
 * @CreateTime 2025/1/31 18:10
 * @Description Redis分布式锁工具
 */
@Component
public class RedisDistributedLockUtil {

    /********************* Redis分布式锁 Simple START **********************/

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取分布式锁，支持阻塞等待
     *
     * 推荐方式：使用原子命令 SET resource_name unique_value NX PX 30000
     * ✅ 优势：单命令原子操作，无竞态条件
     * ⚠️ 限制：无法实现复杂逻辑（如可重入锁）
     *
     * 脚本使用场景：
     *     需要实现锁重入（判断线程标识+计数）
     *     需要组合多个操作（如：判断锁归属+更新超时时间）
     *     需要执行复合条件逻辑
     *
     * 结论：基础锁获取无需脚本，但复杂场景需要脚本保障原子复合操作。
     *
     * @param lockKey 锁的键
     * @param requestId 请求ID（唯一标识）
     * @param expireTime 锁的过期时间（毫秒）
     * @param waitTime 等待锁的最大时间（毫秒）
     * @return 是否获取到锁
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime) {
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < waitTime) {
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
            if (result != null && result) {
                return true; // 获取锁成功
            }
            try {
                Thread.sleep(10); // 短暂休眠，避免过度占用CPU
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false; // 超过等待时间仍未获取到锁
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的键
     * @param requestId 请求ID（唯一标识）
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        // 定义 Lua 脚本
        String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<>(scriptText, Long.class);
        // 执行脚本
        Long result = stringRedisTemplate.execute(
                script,
                Collections.singletonList(lockKey), // KEYS[1]
                requestId // ARGV[1]
        );
        // 判断是否释放成功
        return result != null && result.equals(1L);
    }

    /********************* Redis分布式锁 Simple END **********************/

    /************************** Redisson START ***************************/

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 获取分布式锁并执行业务逻辑（自动续期）
     *
     * @param lockKey 锁的Key
     * @param waitTime 获取锁的最大等待时间（秒）
     * @param supplier 业务逻辑函数
     * @return 业务执行结果
     */
    public <T> T executeWithLock(String lockKey, long waitTime, Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁，最多等待 waitTime 秒，锁自动释放时间60秒（看门狗会自动续期）
            if (lock.tryLock(waitTime, 60, TimeUnit.SECONDS)) {
                try {
                    // 执行业务逻辑
                    return supplier.get();
                } finally {
                    // 释放锁
                    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                throw new DistributedLockException("获取分布式锁失败，lockKey: " + lockKey);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new DistributedLockException("获取分布式锁被中断", e);
        }
    }

    /**
     * 获取公平锁（按请求顺序获取锁）
     *
     * @param lockKey 锁的Key
     * @param waitTime 获取锁的最大等待时间（秒）
     * @param leaseTime 锁持有时间（秒）
     * @param supplier 业务逻辑函数
     * @return 业务执行结果
     */
    public <T> T executeWithFairLock(String lockKey, long waitTime, long leaseTime, Supplier<T> supplier) {
        RLock lock = redissonClient.getFairLock(lockKey);
        try {
            if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) {
                try {
                    return supplier.get();
                } finally {
                    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                throw new DistributedLockException("获取公平锁失败，lockKey: " + lockKey);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new DistributedLockException("获取公平锁被中断", e);
        }
    }

    /************************* Redisson END **************************/
}

// 自定义分布式锁异常
class DistributedLockException extends RuntimeException {
    public DistributedLockException(String message) {
        super(message);
    }

    public DistributedLockException(String message, Throwable cause) {
        super(message, cause);
    }
}